/*
 * $Id: ConceptManager.java 137 2009-07-16 09:46:22Z donbeave $
 */

package labour.concept.client.data;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import labour.concept.client.ConceptConnector;

import com.extjs.gxt.ui.client.data.BaseModelData;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.store.ListStore;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class ConceptManager {

  protected ConceptConnector connector;

  protected Locale defaultLocale;

  protected ListStore<Locale> localesStore;
  protected Boolean localesLoaded = false;

  protected ListStore<ObjectType> typesStore;
  protected Boolean typesLoaded = false;

  protected ListStore<DataType> dataTypesStore;
  protected Boolean dataTypesLoaded = false;

  protected ListStore<BaseObject> objectsStore;

  public ConceptManager(ConceptConnector connector) {
    this.connector = connector;

    localesStore = new ListStore<Locale>();
    typesStore = new ListStore<ObjectType>();
    dataTypesStore = new ListStore<DataType>();
    objectsStore = new ListStore<BaseObject>();
  }

  public void initialize(final AsyncCallback<Object> callback) {
    connector.loadBasicData(new AsyncCallback<Map<String, ?>>() {
      public void onFailure(Throwable caught) {
        callback.onFailure(caught);
      }

      @SuppressWarnings("unchecked")
      public void onSuccess(Map result) {
        processBasicData(result);

        // TODO переделать информацию возвращаемую после запроса
        callback.onSuccess(result);
      }
    });
  }

  public void loadLocales(AsyncCallback<List<Locale>> callback) {
    // TODO обновление локалей
  }

  public void loadObjectsTypes(AsyncCallback<List<ObjectType>> callback) {
    // TODO обновление типов объектов
  }

  public void loadDataTypes(AsyncCallback<List<DataType>> callback) {
    // TODO обновление списка типов данных
  }

  @SuppressWarnings("unchecked")
  public void loadObjectById(String id, AsyncCallback callback) {
    BaseObject object = objectsStore.findModel(ObjectModel.ID, id);

    // if object is loaded, returning it, if is not loaded, first loading it, the return it
    if (object != null) {
      callback.onSuccess(object);
    } else {
      // TODO подгружаем объект
    }
  }

  @SuppressWarnings("unchecked")
  public void loadObjects(Object loadConfig, final AsyncCallback callback) {
    getConnector().loadObjects(loadConfig, new AsyncCallback<ObjectsLoadResult<ObjectModel>>() {
      public void onFailure(Throwable caught) {
        callback.onFailure(caught);
      }

      public void onSuccess(ObjectsLoadResult<ObjectModel> result) {
        processObjects(result.getData());
        processObjectsTexts(result.getTexts());

        callback.onSuccess(getObjects(result.getData()));
      }
    });
  }

  public List<Locale> getLocales() {
    return localesStore.getModels();
  }

  public Locale getLocaleById(String id) {
    return localesStore.findModel(LocaleModel.ID, id);
  }

  public Locale getLocaleByLanguage(String language) {
    return localesStore.findModel(LocaleModel.LANGUAGE, language);
  }

  public Locale getDefaultLocale() {
    if (defaultLocale == null) {
      defaultLocale = getLocales().get(0);
    }
    return defaultLocale;
  }

  public void setDefaultLocale(Locale locale) {
    defaultLocale = locale;
  }

  public List<ObjectType> getObjectsTypes() {
    return typesStore.getModels();
  }

  public ObjectType getObjectTypeById(String id) {
    return typesStore.findModel(ObjectTypeModel.ID, id);
  }

  public ObjectType getObjectTypeByName(String name) {
    return typesStore.findModel(ObjectTypeModel.NAME, name);
  }

  public List<DataType> getDataTypes() {
    return dataTypesStore.getModels();
  }

  public DataType getDataTypeById(String id) {
    return dataTypesStore.findModel(DataTypeModel.ID, id);
  }

  public DataType getDataTypeByName(String name) {
    return dataTypesStore.findModel(DataTypeModel.NAME, name);
  }

  public BaseObject createObject(ObjectType type, String name, Locale locale) {
    BaseObject object = createObject(type);
    object.extendData(locale, name);
    return object;
  }

  public ObjectText createText(BaseObject object, Locale locale, String name) {
    ObjectTextModel model = new ObjectTextModel();
    model.set(ObjectTextModel.LOCALE_ID, locale.getModel().getId());
    model.set(ObjectTextModel.OBJECT_ID, object.getModel().getId());
    model.set(ObjectTextModel.OBJECT_UID, object.getModel().getUId());
    model.set(ObjectTextModel.NAME, name);

    ObjectText text = createObjectText(model);

    return text;
  }

  public List<ObjectText> getTextsList(List<? extends BaseObject> list, Locale locale) {
    ArrayList<ObjectText> texts = new ArrayList<ObjectText>();

    for (BaseObject object : list) {
      if (object.hasData(locale)) {
        texts.add(object.getData(locale));
      }
    }

    return texts;
  }

  @SuppressWarnings("unchecked")
  public void saveObject(final BaseObject object, final AsyncCallback callback) {
    List<ObjectTextModel> textsModels = new ArrayList<ObjectTextModel>();

    for (ObjectText text : object.getTextsStore().getModels()) {
      textsModels.add(text.getModel());
    }

    connector.saveObject(object.getModel(), textsModels, new AsyncCallback() {
      public void onFailure(Throwable caught) {
        callback.onFailure(caught);
      }

      public void onSuccess(Object result) {
        if (object.hasParent()) {
          int childrenCount = object.getParent().getModel().getChildrenCount() + 1;

          object.getParent().getModel().set(ObjectModel.CHILDREN_COUNT, childrenCount);
        }

        callback.onSuccess(null);
      }
    });
  }

  @SuppressWarnings("unchecked")
  public void deleteObject(final BaseObject object, final AsyncCallback callback) {
    connector.deleteObject(object.getModel(), new AsyncCallback() {
      public void onFailure(Throwable caught) {
        callback.onFailure(caught);
      }

      public void onSuccess(Object result) {
        if (object.hasParent()) {
          int childrenCount = object.getParent().getModel().getChildrenCount() - 1;

          object.getParent().getModel().set(ObjectModel.CHILDREN_COUNT, childrenCount);
        }

        callback.onSuccess(null);
      }
    });
  }

  public Boolean isLocalesLoaded() {
    return localesLoaded;
  }

  public Boolean isObjectsTypesLoaded() {
    return typesLoaded;
  }

  public Boolean isDataTypesLoaded() {
    return dataTypesLoaded;
  }

  public ConceptConnector getConnector() {
    return connector;
  }

  protected BaseObject getObjectById(String id) {
    return objectsStore.findModel(ObjectModel.ID, id);
  }

  protected BaseObject getObjectByUId(String uid) {
    return objectsStore.findModel(ObjectModel.UID, uid);
  }

  protected BaseObject createObject(ObjectType type) {
    ObjectModel model = new ObjectModel();
    model.set(ObjectModel.TYPE_ID, type.getModel().getId());

    return createObject(model);
  }

  protected List<BaseObject> getObjects(List<? extends ObjectModel> models) {
    List<BaseObject> list = new ArrayList<BaseObject>();

    for (ObjectModel model : models) {
      if (model.isExist()) {
        list.add(getObjectById(model.getId()));
      } else {
        list.add(getObjectByUId(model.getUId()));
      }
    }

    return list;
  }

  @SuppressWarnings("unchecked")
  protected void processBasicData(Map data) {
    List<LocaleModel> locales = (List<LocaleModel>) data.get("locales");

    if (!locales.isEmpty()) {
      processLocales(locales);
      localesLoaded = true;
    }

    List<ObjectTypeModel> objectsTypes = (List<ObjectTypeModel>) data.get("objectsTypes");

    if (!objectsTypes.isEmpty()) {
      processObjectsTypes(objectsTypes);
      typesLoaded = true;
    }

    List<DataTypeModel> dataTypes = (List<DataTypeModel>) data.get("dataTypes");

    if (!dataTypes.isEmpty()) {
      processDataTypes(dataTypes);
      dataTypesLoaded = true;
    }
  }

  protected void processLocales(List<? extends LocaleModel> locales) {
    processIdModels(locales, localesStore);
  }

  protected void processObjectsTypes(List<? extends ObjectTypeModel> types) {
    processIdModels(types, typesStore);
  }

  protected void processDataTypes(List<? extends DataTypeModel> dataTypes) {
    processIdModels(dataTypes, dataTypesStore);
  }

  protected void processObjects(List<? extends ObjectModel> objects) {
    processIdModels(objects, objectsStore);
  }

  protected void processObjectsTexts(List<? extends ObjectTextModel> texts) {
    for (ObjectTextModel model : texts) {
      BaseObject object;

      if (model.isExist()) {
        object = getObjectById(model.getObjectId());
      } else {
        object = getObjectByUId(model.getObjectUId());
      }

      Locale locale = getLocaleById(model.getLocaleId());

      if (object.hasData(locale)) {
        ObjectTextModel text = object.getData(locale).getModel();
        text.setProperties(model.getProperties());
      } else {
        object.extendData(locale, model.getName());

        ObjectTextModel text = object.getData(locale).getModel();
        text.setProperties(model.getProperties());
      }
    }
  }

  @SuppressWarnings("unchecked")
  protected void processIdModels(List<? extends BaseModelData> models, ListStore store) {
    // filling the models store
    for (BaseModelData model : models) {
      if (!(model instanceof IdModel)) {
        // throw the error!
      }

      IdModel idModel = (IdModel) model;

      XModel original;

      if (idModel.isExist()) {
        original = (XModel) store.findModel(IdModel.ID, idModel.getId());
      } else {
        original = (XModel) store.findModel(IdModel.UID, idModel.getUId());
      }

      if (original == null) {
        // adding model to store
        ModelData item = createModel(model);

        store.add(item);
      } else {
        // updating model data
        BaseModelData originalModel = (BaseModelData) original.getModel();
        originalModel.setProperties(model.getProperties());
      }
    }
  }

  protected ModelData createModel(BaseModelData model) {
    if (model instanceof LocaleModel) {
      return createLocale((LocaleModel) model);
    }
    else if (model instanceof ObjectTypeModel) {
      return createObjectType((ObjectTypeModel) model);
    }
    else if (model instanceof DataTypeModel) {
      return createDataType((DataTypeModel) model);
    }
    else if (model instanceof ObjectModel) {
      return createObject((ObjectModel) model);
    }
    else if (model instanceof ObjectTextModel) {
      return createObjectText((ObjectTextModel) model);
    }
    // throw the error!
    return null;
  }

  protected Locale createLocale(LocaleModel model) {
    return new Locale(model, this);
  }

  protected ObjectType createObjectType(ObjectTypeModel model) {
    return new ObjectType(model, this);
  }

  protected DataType createDataType(DataTypeModel model) {
    return new DataType(model, this);
  }

  protected BaseObject createObject(ObjectModel model) {
    return new BaseObject(model, this);
  }

  protected ObjectText createObjectText(ObjectTextModel model) {
    return new ObjectText(model, this);
  }

}
